
function processBooking(booking) {
  
  // Validate booking data
  {
    var errs = [];
    if(!validateBooking_(booking, errs)) {
      Logger.log("Booking validation failure: %s", errs.toString());
      // Going to report this to admin and stop here.
      sendAdminMail_(booking, errs);
      sendReviewedEmail_(booking);
      return false;
    }
    Logger.log("Booking validated OK");
  }
  
  // Add times to the booking
  addTimesToBooking_(booking);
  
  // Check if slot is free in calendar
  {
    var errs = [];
    var warns = [];
    if(!isBookingAvailable_(booking, errs, warns)) {
      Logger.log("Booking slot is not available: %s", errs.toString());
      // Going to report this to admin and stop here.
      sendAdminMail_(booking, errs);
      sendReviewedEmail_(booking);
      return false;
    }
    Logger.log("Booking slot is available");
  }

  booking.state = "PROVISIONAL";
  
  booking.docs.eventID = createEvent_(booking);
  Logger.log("Event created: %s", booking.docs.eventID);
  
  // Generate invoice
  calculateBillingLines_(booking);
  calculatePayments_(booking);
  
  booking.docs.invoiceID = createInvoice_(booking).invoiceID;
  Logger.log("Invoice created: %s", booking.docs.invoiceID);
  
  // Create entry in provisional bookings sheet
  addProvisionalBookingRecord_(booking);
  
  // Check if an outstanding invoice is due
  if(booking.invoice.total == 0) {
    // No payment due, confirm booking
    confirmBooking_(booking);
  }
}


/**
 * Adds times to arrival date and departure date depending on event type.
 */
function addTimesToBooking_(booking) {

  booking.arrivalDate.setHours(0);
  booking.departureDate.setHours(0);
  
  // Always top of the hour
  booking.arrivalDate.setMinutes(0);
  booking.arrivalDate.setSeconds(0);
  booking.departureDate.setMinutes(0);
  booking.departureDate.setSeconds(0);
  
  // Override event time setting for multi-day bookings and residentials
  if(booking.departureDate.getTime() > booking.arrivalDate.getTime()) {
    // Booking is over more than one day
    booking.arrivalDate.setHours(18);
    booking.departureDate.setHours(16);
  } else if(booking.eventType == EVENT_TYPE_RESIDENTIAL) {
    if(booking.departureDate.getTime() > booking.arrivalDate.getTime()) {
      // Booking is over more than one day
      booking.arrivalDate.setHours(18);
      booking.departureDate.setHours(16);
    } else {
      // Booking is only one day
      booking.arrivalDate.setHours(9);
      booking.departureDate.setHours(22);
    }
  } else {
    // Set times based on event time setting
    if(booking.eventTime == EVENT_TIME_DAYTIME) {
      booking.arrivalDate.setHours(9);
      booking.departureDate.setHours(17);
    } else if(booking.eventTime == EVENT_TIME_EVENING) {
      booking.arrivalDate.setHours(18);
      booking.departureDate.setHours(22);
    } else {
      booking.arrivalDate.setHours(9);
      booking.departureDate.setHours(22);
    }
  }
  Logger.log("Set booking arrival time: " + booking.arrivalDate + "; departure time: " + booking.departureDate);
  
  // Calculate event hours and days  
  booking.eventHours = Math.ceil((booking.departureDate - booking.arrivalDate) / 3600000);
  booking.eventDays = Math.ceil(booking.eventHours / 24) + (isOvernightBooking_(booking) ? 1: 0);
  Logger.log("Calculated booking days: " + booking.eventDays + " (" + booking.eventHours + " hours)");
}

/**
 * Converts booking to an array of data.
 */
function bookingToRowData_(booking) {
  return [
      booking.timestamp
    , booking.eventName
    , booking.organisation.name
    , booking.organisation.type
    , booking.contact.name
    , booking.contact.telephone
    , booking.contact.email
    , booking.eventType
    , booking.isHouseBooking
    , booking.isFieldBooking
    , booking.equipment.fullyEquipped
    , booking.equipment.tents
    , booking.arrivalDate
    , booking.departureDate
    , booking.eventHours
    , booking.eventDays
    , booking.participants
    , booking.leaders
    , booking.notes
    , booking.state
    , booking.docs.eventID
    , booking.docs.invoiceID
  ];
}

function getEventDescription_(booking) {
  var desc = "";
  if(booking.isHouseBooking && !booking.isFieldBooking) {
    desc = "Ellesborough house, ";
  }
  if(!booking.isHouseBooking && booking.isFieldBooking) {
    desc = "Ellesborough field, ";
  }
  if(booking.isHouseBooking && booking.isFieldBooking) {
    desc = "Ellesborough house and field, ";
  }
  
  desc += booking.arrivalDate.toLocaleDateString() + " " + booking.arrivalDate.toLocaleTimeString() + " - " +
    (isOvernightBooking_(booking) ? booking.departureDate.toDateString() + " " : "") + booking.departureDate.toLocaleTimeString();
    
  return desc;
}

/**
 * Checks if the booking is an overnight booking.
 * @return if the booking spans multiple days.
 */
function isOvernightBooking_(booking) {
  return (booking.arrivalDate.getDate() != booking.departureDate.getDate()) &&
    (booking.arrivalDate.getMonth() != booking.departureDate.getDate()) &&
      (booking.arrivalDate.getMonth() != booking.departureDate.getDate());
}

/**
 * Checks if this is a test booking.
 * @return if the booking has a test organisation.
 */
function isTestBooking_(booking) {
  return (booking.organisation.type == ORG_TYPE_TEST);
}

/**
 * Converts an array of data to a booking.
 */
function rowDataToBooking_(rowData) {
  return {
      "timestamp"       : rowData[0]
    , "eventName"       : rowData[1]
    , "organisation"    : {
          "name" : rowData[2]
        , "type" : rowData[3]
      }
    , "contact"         : {
          "name"      : rowData[4]
        , "telephone" : rowData[5]
        , "email"     : rowData[6]
      }
    , "eventType"       : rowData[7]
    , "isHouseBooking"  : rowData[8]
    , "isFieldBooking"  : rowData[9]
    , "equipment"       : {
          "fullyEquipped" : rowData[10]
        , "tents"         : rowData[11]
      }
    , "arrivalDate"     : rowData[12]
    , "departureDate"   : rowData[13]
    , "eventHours"      : rowData[14]
    , "eventDays"       : rowData[15]
    , "participants"    : rowData[16]
    , "leaders"         : rowData[17]
    , "notes"           : rowData[18]
    , "state"           : rowData[19]
    , "docs" : {
          "eventID"   : rowData[20]
        , "invoiceID" : rowData[21]
      }
  };
}

/**
 * Validates a booking against business rules.
 */
function validateBooking_(booking, errs) {
  
  if(errs == null) {
    errs = [];
  }
  
  // Check booking dates
  var today = new Date();
  
  if(booking.arrivalDate < today) {
    // Arriving in the past
    errs.push("Arrival date has passed.");
    return false;
  }
  
  if(booking.departureDate < today) {
    // Departing in the past
    errs.push("Departure date has passed.");
    return false;
  }
  
  if(booking.departureDate < booking.arrivalDate) {
    // Departing before arriving
    errs.push("Departure date is before arrival date");
    return false;
  }
  
  // Check that booking is not for next year if we're not yet beyond August
  if(today.getMonth() < 7 && booking.arrivalDate.getYear() > today.getYear()) {
    // Today's month is before August and booking is for next year
    errs.push("Booking is outside of open booking period");
    return false;
  } 
  
  // Check at least a house or field booking requested
  if(!booking.isHouseBooking && !booking.isFieldBooking) {
    // Not booked either house or field
    errs.push("No house or field booking requested.");
    return false;
  }
  
  // Check group sizes
  if(booking.isHouseBooking && isOvernightBooking_(booking) && (booking.participants + booking.leaders) > 30) {
    // House only sleeps 30
    errs.push("Too many attendees for overnight house booking");
    return false;
  }
  if(booking.isFieldBooking && isOvernightBooking_(booking) && (booking.participants + booking.leaders) > 60) {
    // Field only sleeps 60
    errs.push("Too many attendees for overnight field booking");
    return false;
  }
  
  // Check for other irregularities
  if(!booking.isHouseBooking && booking.eventType.indexOf("Meeting") >= 0) {
    // Meeting in the field?
    errs.push("Meeting time slot booked but no house booking made");
    return false;
  }
  
  if((booking.eventType.indexOf(EVENT_TYPE_MEETING) >= 0) && isOvernightBooking_(booking)) {
    // Long meeting...
    errs.push("Meeting booking spanning multiple days.");
    return false;
  }
  
  booking.state = "VALID";
    
  return true;
}
